package it.unibo.refolding.pico.mfp;

import it.unimi.dsi.fastutil.bytes.ByteList;

import java.nio.ByteBuffer;

import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;

public class WaveHeader2 extends WaveHeader {

  final ByteList unsignedXUnits;
  final double hsA, hsB;

  /**
   * Maximum length of wave name in version 1 and 2 files. Does not include the
   * trailing null.
   */
  private static final int MAX_WAVE_NAME2 = 18;

  public WaveHeader2(short type, ByteList unsignedBname, ByteList unsignedDataUnits,
    ByteList unsignedXUnits, int npnts, double hsA, double hsB, short fsValid, double topFullScale,
    double botFullScale, int unsignedCreationDate, int unsignedModDate) {
    super(
      type, unsignedBname, unsignedDataUnits, npnts, unsignedCreationDate, unsignedModDate,
      fsValid, topFullScale, botFullScale);
    this.unsignedXUnits = unsignedXUnits;
    this.hsA = hsA;
    this.hsB = hsB;
  }
  
  @Override
  public boolean equalsCommonFields(WaveHeader other) {
    if (other instanceof WaveHeader2) {
      return equals(other);
    } else {
      return super.equalsCommonFields(other);
    }
  }
  
  static Checksummed<WaveHeader2> read(ByteBuffer bb) {
    int startPos = bb.position();
    int unsignedChecksum = 0;
    short type = bb.getShort();
    unsignedChecksum = IgorUtil.addShort(type, unsignedChecksum);
    /*
     * Skip: struct WaveHeader2 **next; // Used in memory only. Write zero.
     * Ignore on read.
     */
    unsignedChecksum = IgorUtil.skip(IgorUtil.POINTER_SIZE, unsignedChecksum, bb);
    // Warning: char bname[MAX_WAVE_NAME2+2], not char bname[MAX_WAVE_NAME2+1].
    Checksummed<ByteList> checksummedUnsignedBname = IgorUtil.readString(MAX_WAVE_NAME2 + 1, bb);
    unsignedChecksum = IgorUtil.addShort(
      checksummedUnsignedBname.unsignedChecksum, unsignedChecksum);
    ByteList unsignedBname = checksummedUnsignedBname.object;
    /*
     * Skip: short whVersion; // Write 0. Ignore on read. short srcFldr; // Used
     * in memory only. Write zero. Ignore on read. Handle fileName; // Used in
     * memory only. Write zero. Ignore on read.
     */
    unsignedChecksum = IgorUtil.skip(2 * 2 + IgorUtil.POINTER_SIZE, unsignedChecksum, bb);
    Checksummed<ByteList> checksummedUnsignedDataUnits = IgorUtil.readString(
      IgorUtil.MAX_UNIT_CHARS, bb);
    unsignedChecksum = IgorUtil.addShort(
      checksummedUnsignedDataUnits.unsignedChecksum, unsignedChecksum);
    ByteList unsignedDataUnits = checksummedUnsignedDataUnits.object;
    Checksummed<ByteList> checksummedUnsignedXUnits = IgorUtil.readString(
      IgorUtil.MAX_UNIT_CHARS, bb);
    unsignedChecksum = IgorUtil.addShort(
      checksummedUnsignedXUnits.unsignedChecksum, unsignedChecksum);
    ByteList unsignedXUnits = checksummedUnsignedXUnits.object;
    int npnts = bb.getInt();
    unsignedChecksum = IgorUtil.addInt(npnts, unsignedChecksum);
    /*
     * Skip: short aModified; // Used in memory only. Write zero. Ignore on
     * read.
     */
    unsignedChecksum = IgorUtil.skip(2, unsignedChecksum, bb);
    double hsA = bb.getDouble();
    unsignedChecksum = IgorUtil.addDouble(hsA, unsignedChecksum);
    double hsB = bb.getDouble();
    unsignedChecksum = IgorUtil.addDouble(hsB, unsignedChecksum);
    /*
     * Skip: short wModified; // Used in memory only. Write zero. Ignore on
     * read. short swModified; // Used in memory only. Write zero. Ignore on
     * read.
     */
    unsignedChecksum = IgorUtil.skip(2 * 2, unsignedChecksum, bb);
    short fsValid = bb.getShort();
    unsignedChecksum = IgorUtil.addShort(fsValid, unsignedChecksum);
    double topFullScale = bb.getDouble();
    unsignedChecksum = IgorUtil.addDouble(topFullScale, unsignedChecksum);
    double botFullScale = bb.getDouble();
    unsignedChecksum = IgorUtil.addDouble(botFullScale, unsignedChecksum);
    /*
     * Skip: char useBits; // Used in memory only. Write zero. Ignore on read.
     * char kindBits; // Reserved. Write zero. Ignore on read. void **formula;
     * // Used in memory only. Write zero. Ignore on read. long depID; // Used
     * in memory only. Write zero. Ignore on read.
     */
    unsignedChecksum = IgorUtil.skip(2 * 1 + IgorUtil.POINTER_SIZE + 4, unsignedChecksum, bb);
    int unsignedCreationDate = bb.getInt();
    unsignedChecksum = IgorUtil.addInt(unsignedCreationDate, unsignedChecksum);
    /*
     * Skip: char wUnused[2]; // Reserved. Write zero. Ignore on read.
     */
    unsignedChecksum = IgorUtil.skip(2 * 1, unsignedChecksum, bb);

    int unsignedModDate = bb.getInt();
    unsignedChecksum = IgorUtil.addInt(unsignedModDate, unsignedChecksum);
    /*
     * Skip: Handle waveNoteH; // Used in memory only. Write zero. Ignore on
     * read.
     */
    unsignedChecksum = IgorUtil.skip(IgorUtil.POINTER_SIZE, unsignedChecksum, bb);
    assert byteCount() == bb.position() - startPos : byteCount() + " != "
      + (bb.position() - startPos);
    return new Checksummed<>(new WaveHeader2(
      type, unsignedBname, unsignedDataUnits, unsignedXUnits, npnts, hsA, hsB, fsValid,
      topFullScale, botFullScale, unsignedCreationDate, unsignedModDate), (short) unsignedChecksum);
  }

  static int byteCount() {
    return 2 + IgorUtil.POINTER_SIZE + (MAX_WAVE_NAME2 + 2) + 2 * 2 + IgorUtil.POINTER_SIZE
      + (IgorUtil.MAX_UNIT_CHARS + 1) + (IgorUtil.MAX_UNIT_CHARS + 1) + 4 + 2 + 2 * 8 + 3 * 2 + 2
      * 8 + 2 * 1 + IgorUtil.POINTER_SIZE + 2 * 4 + 2 + 4 + IgorUtil.POINTER_SIZE;
  }

  @Override
  public String toString() {
    return new ReflectionToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).setExcludeFieldNames(
      "unsignedCreationDate", "unsignedModDate", "unsignedBname", "unsignedDataUnits",
      "unsignedDimUnits").append("creationDate", creationDate()).append("modDate", modDate()).append(
      "bname", bname()).append("dataUnits", dataUnits()).append("xUnits", xUnits()).build();
  }

  public String xUnits() {
    return IgorUtil.unsignedBytesToString(unsignedXUnits);
  }

  @Override
  public WaveHeader2 withBname(String bname) {
    return new WaveHeader2(
      type, IgorUtil.stringToUnsignedBytes(bname), unsignedDataUnits, unsignedXUnits, npnts, hsA,
      hsB, fsValid, topFullScale, botFullScale, unsignedCreationDate, unsignedModDate);
  }
}
